Product Manager Anna has a contract for a new project. After giving it some thought for a few days, she commissioned developer Bernd to work out the initial designs and proposals. What is needed first of all is a system that can record personal data, save several data records in a list and filter or search them according to different criteria, while being as flexible as possible. Since the client is still not able to define the final specifications, changes should be possible at any time.
Bernd, an experienced and pragmatic developer, is of course familiar with these types of contracts and starts by putting together some basic ideas to tackle the problem. So: 1st, a data structure to specify a person and 2nd, a list to hold any number of such persons is needed. Additionally, a mechanism to browse the list for specific criteria, is also required. So far so good, Bernd thinks and fills in his code editor (Listing 1). The declaration of the data fields as public in the class Person can basically be seen as critical, because here stability is not given after the creation of an instance . This should be tolerated in further course, since it is not relevant for the given examples!
class Person { public $firstname; public $lastname; // ... you can add more fields later here function __construct(string $first, string $last) { $this->firstname = $first; $this->lastname = $last; } function __toString() { return $this->firstname.' '.$this->lastname; } } class PersonList { private $_myList = array(); function add(Person $person) { $this->_myList[] = $person; } function show() { foreach($this->_myList as $person) echo $person.' '; } } $population = new PersonList; $population->add( new Person('Micky' , 'Mouse') ); $population->add( new Person('Donald', 'Duck') ); $population->add( new Person('Goofy') ); $population->show();
Once Bernd had keyed in the final program code and wanted to try it out, an error message promptly appeared in the browser. “That makes sense!” thinks Bernd, because he did not specify a surname when creating the instance for ‘Goofy’, which of course PHP acknowledges with an error. What could Bernd do to avoid this error in the future?
The parameter dilemma
The constructor of a class is defined in PHP as a special class method, function __construct ([parameter list]). Unfortunately, unlike other programming languages, PHP does not allow function overloading. It is therefore not possible to declare several functions with the same name but with different parameters. PHP allows you to assign function parameters with default values, whereby the sequence must be strictly followed. An attempt to call a function with one, two or even any number of parameters is possible via a variable parameter list, yet it requires additional logic to handle the parameters correctly.
Since Anna had promised that the customer could change the fields in the Person class at any time, a different approach is needed, Bernd figures. In the first instance, the Person class is a pure “data container” – a tuple that does not contain complex logic at first. Why, Bernd asked himself, does the class need a constructor at all and couldn’t he assign reasonably meaningful default values to have a defined “basic state” when creating an instance? So now Bernd cut out the constructor from the class Person and declared (meaningful) default values for the fields. For example, in strings, an empty string can be provided as the default value. NULL is usually not a good default value because you run the risk of falling into an undefined state. As we will see later however, NULL may be quite justified for certain fields.
After Bernd made the changes to Person, the instantiation of the individual objects needed to be adjusted. For this purpose, a new instance of Person was first assigned to the temporary variable $person, then the fields of the object were described with data and finally, the object was appended to the list (Listing 2).
class Person { public $firstname = ''; public $lastname = ''; //... function __toString() { return $this->firstname.' '.$this->lastname; } } class PersonList { //... as in Listing 1 } $population = new PersonList; $person = new Person; $person->firstname = 'Micky'; $person->lastname = 'Mouse'; $population->add($person); $person = new Person; $person->firstname = 'Donald'; $person->lastname = 'Duck'; $population->add($person); $person = new Person; $person->firstname = 'Goofy'; $population->add($person); $population->show(); // For parameterless constructors, PHP allows the abbreviated notation new CLASSNAME; empty parentheses () are not specified!
Admittedly, the notation is not as short and elegant as in Listing 1 now, but it is much more flexible on the other hand, as the declaration of Goofy shows – only one field is assigned here.
Bernd reflects on how he could make the code better. For each person you have to 1.) save an instance in a variable, 2.) set the values and 3.) add the object to the list. Steps 1.) and 3.) are always the same. It would be nice to have a mechanism that would “outsource” these repetitive steps to a function. But then we would need a function which receives the instance of a Person as a parameter; then in this function the fields of Person could be set and finally Person would be added to the list.
Higher order
Of course, as an object-oriented programming language, PHP also knows mechanisms that can deal with anonymous functions. Such functions are also referred to as lambda functions or lambda for short. On the one hand, functions can be assigned as values to variables, but it is also possible to declare a function body as a parameter when calling another function. Functions that are given functions as parameters are then referred to as higher-order functions. These can often make the life of a developer that much easier. However, we cannot forget that the readability and comprehensibility of source code can be significantly impaired if it is not written carefully and in a structured manner. You can find a myriad of negative examples for this online. The JavaScript Community in particular has some exceptionally spine-chilling blunders.
Figure 1 is intended to illustrate what the function you are looking for may look like. To this end, Bernd changes the add() function in the PersonList class. A new instance $p = new Person; will be generated and with this new object, the passed function $config($p); will be called.
Since the PersonList class represents a list of person objects, Bernd would find it very practical to have an easy way to iterate through the list of entries with just one loop. This would of course be quite possible if outside access to the _myList private array would be allowed, so he could go through the list at any time using a foreach () loop. Yet Bernd does not want to implement a public property for read-only access to _myList. Finally, he wants to preserve the maximum integrity of the class and already has an idea as to how he could make it possible by means of a higher order function.
In principle, it should be sufficient to have only one loop, which would always be used to iterate through the list. This function also receives a function as a parameter, which is then called for each list element and with which it can work. The implementation will now be easy for Bernd and the new class for PersonList looks quite clean (Listing 3).
//Person class as above ... ... class PersonList { private $_myList = array(); function add(callable $config) { $p = new Person; $this->_myList[] = $p; $config($p); } function each(callable $action) { foreach($this->_myList as $p) $action($p); } } $population = new PersonList; $population->add( function(Person $p) { $p->firstname = 'Micky'; $p->lastname = 'Mouse'; }); $population->add( function(Person $p) { $p->firstname = 'Goofy'; }); $population->add( function(Person $p) { $p->firstname = 'Donald'; $p->lastname = 'Duck'; }); ... $population->each( function(Person $p) { echo $p.' '; });
Bernd added the method each(callable$action) to the PersonList class, which basically represents an encapsulation to the PHP construct foreach. The parameter $action points to a lambda, which is called for each element of the internal list. As of PHP 7, function parameters should always be marked with the corresponding data type, in this case callable. This ensures better readability and a reduced error rate of the source code. In PHP, callable is a data type for functions. Unfortunately, PHP does not differentiate between different function signatures, which can be a source of error that is difficult to trace.
The advantage here is clear: Using a single class method, each(callable$action), we can now iterate through the list. That’s why Bernd deleted the show() method and implemented the output of persons at the end of the program.
Search and Find
As Anna had already promised, the client wants to add more properties (fields) to the person class during the course of project development. No problem for Bernd, as he can now add these new properties to his Person class at any time.
We should note at this point that the personal data in a real application will come from a type of database of course and will not be allocated individually. Naturally, an iterator will be used for this purpose. For simplicity sake, this example uses discrete allocation. The second example shows a database access scenario. In addition, the customer would like to have the possibility of searching the list by different properties of persons or of using different filters.
Function variables in PHP
In addition to data types for int or string, PHP has its own type for function variables. This type is callable and should always be entered in the function declaration for the sake of completeness.
PHP knows different ways to allocate a function to a variable.
function calc(int $x): int { return $x + 1; } function test(callable $fn) { echo $fn(1); }
Quite often (and as we like to do in our examples), a function can be declared anonymously and directly as a parameter when called. However, it is crucial that you pay attention to good legibility!
test( function(int $x): int { return $x + 1; } );
A function as a value can also be allocated to a variable:
$myFunc = function(int $x): int { return $x + 1; }; test( $myFunc );
For globally declared functions, the fully qualified name, or NAMESPACE \ NAME, must be noted:
test( '\calc' );
A convenient solution is to organize functions in classes (including static) into libraries. To call the function, the function parameter must be declared as an array with two elements. First you enter the fully qualified name of the class, followed by the name of the desired function:
class CalcLib { static function calc10(int $x): int { return $x * 10; } static function calc15(int $x): int { return $x * 15; } static function calc75(int $x): int { return $x * 75; } } test( ['\CalcLib', 'calc10'] ); test( ['\CalcLib', 'calc15'] ); test( ['\CalcLib', 'calc75'] );
Lastly, object methods can of course be used as function sources. The parameter array is also used for this, but the instance object is used as the first element instead of the name of the class.
class CalcLibObj { public $factor = 0; function calc(int $x): int { return $x * $this->factor; } } $myLib = new CalcLibObj; test( [$myLib, 'calc'] );
Anonymous functions are explained very well in the PHP online documentation [1].
The “Function variables in PHP” box illustrates how function variables can be written.
Bernd wonders how filtering can take place in a list. First, a test or comparison must be performed on any one property of each list item; for example, all ducks.
if($p->lastname === ‘Duck’) then … – If the result of the test is true, an operation can be performed on the person. Afterwards, the same procedure is applied for the next element of the list. The each(…) method for iteration through all elements has already been implemented and will be used again. So Bernd comes to the method probe (…) which means as much as probing or investigating. It has two parameters. The first is the test function, which returns true or false. The second parameter is also a function that is called depending on the test result. Thus Bernd has found a rather elegant solution to universally search for certain properties in his Persons list. Listing 4 shows the finished version of the PersonList class.
class PersonList { private $_myList = array(); function add(callable $config) { $p = new Person; $this->_myList[] = $p; $config($p); } function each(callable $action) { foreach($this->_myList as $p) $action($p); } function probe(callable $match, callable $action) { $this->each( function(Person $p) use ($match, $action) { if($match($p)) $action($p); }); } }
The PHP Construct use($match, $action) should be noted here as a special feature. If you need an anonymous function to access variables from the parent context, the variables must be “inherited” [1]. The probe(…) method uses the each(…) class method to go over all the elements of the list. Here the method passes a function that first executes the test on each Person $p element and if the test gives a true result, it simply calls the second function with $p. That way Bernd has found a convenient way to search for specific criteria in a list.
Note the following here: A linear search in a (disordered) set of length n is of course unfavorable, since in a worst-case scenario, a comparison must be done n times and is therefore very expensive – a standard problem in computer science. So it would be better to use sorted sets and to store these sets so that they can be used for processing as efficiently as possible. The solution to this issue (no big surprise here) is to use a (SQL) database to store the data of an application. As we had already seen in the first example, PHP also allows functional coding with relatively simple means. We got to know three practical patterns here: the configurator, the iterator and the prober, all of which can be easily used in your own projects.
Database meets functional
In the second simple example, a database with functional code elements is connected for practical purposes to generate HTML dynamically. This requires use of the MySQL demo database CLASSICMODELS, which must be downloaded and installed [2]. To design the output, the example uses the bootstrap UI framework without local installation; the computer should therefore be online to display the page correctly.
Due to the length of the source texts, the listings below only show the relevant code parts. The complete and executable example can be downloaded from Github [3].
After a few weeks, the project is proceeding and Bernd has been making good progress. The client has in the meantime defined the final specifications for the database and provided tables with test data. Since the client has an in-house department for the development and maintenance of the database, Bernd may not use any SQL statements in his application. The data is accessed exclusively through predefined procedures that are created by the client and defined as part of the database. At first, Bernd felt that this approach would take some getting used to, but now he finds it very convenient, since the structure of the data is fully decoupled from the presentation and processing in the application. To query the database, all he needs to do is call a ready-made function with the required parameters and further process the result with PHP.
Functions in the database
The concept of keeping things where they belong can and should be applied when using databases. There, several complex work steps can be collected in a so-called stored procedure and saved under a meaningful name. With consistent use, this eliminates the need to painstakingly assemble SQL queries in the program code of the application; a bug-prone technique that unnecessarily complicates debugging.
It is indeed better to deposit procedures in the database itself. Because modern database engines have sophisticated optimization techniques, a stored procedure often runs faster than an “external” query. In addition, for database development, a specialized application can be integrated into the toolchain that provides advanced features such as syntax check and autocomplete. Such tools are better suited for serious development than popular PHP apps such as phpMyAdmin. For MySQL/MariaDB, MySQL Workbench version 8 works very well and it can be downloaded as a free Community Edition [4].
Listing 5 shows how the stored procedure for our example has been created. First an existing procedure with the specified name is deleted (if one is present). Before the procedure can be created, a delimiter must be specified using DELIMITER. This step is important because a procedure can contain multiple statements separated by semicolons. To recognize the end of the stored procedure, it needs a defined delimiter, in this case a double semicolon (;;). In a database, the tables are usually organized in schemas by means of specific names, in this case CLASSICMODELS . It is common to limit queries on a schema with the SQL statement USE [name]. When writing a query, the name of the schema no longer needs to be specified, as all subsequent queries refer to it.
From the point of view of object-oriented development, increasing the granularity in a database can make sense. It is better to avoid using USE [name]; and basically always enter the name as a prefix before the dot. Although this may mean a little more typing, the reference is clearly given and with the use of a good tool, autocompletion will do it automatically for you anyway.
The classicmodels.GET_CUSTOMERS_BY_COUNTRY procedure has as parameter a string that is used to search for a country in the table. But before the actual searching begins, the variable @country must be set. If the in_country parameter is an empty string, any character will be allocated to @country that can guarantee no result (empty set) during the query. This prevents a situation in which an empty string is passed and the query returns all the rows, therefore a very long list. If the string is not empty, @country is placed between the SQL wildcards % as a search expression. Then the SELECT statement is executed to search the client data.
DROP PROCEDURE IF EXISTS classicmodels.GET_CUSTOMERS_BY_COUNTRY; DELIMITER ;; CREATE PROCEDURE classicmodels.GET_CUSTOMERS_BY_COUNTRY( IN in_country VARCHAR(64) ) BEGIN #-------------------------------------------------------------- IF in_country = '' THEN SET @country = '#'; ELSE SET @country = CONCAT('%', in_country, '%'); END IF; #-------------------------------------------------------------- SELECT customerName AS customer , CONCAT (contactFirstName, '', contactLastName) AS name ,city ,country FROM classicmodels.customers WHERE country LIKE @country ORDER BY country ,city ,customerName; #-------------------------------------------------------------------------- END;; DELIMITER ;
Our PHP sample application includes its own MySQL class for encapsulating the database functions. Henceforth, the listings will only show excerpts of this class. The download of Github contains all of the examples in full.
Functional from the database
Since only stored procedures can be used to access the database and these queries are always the same, the configurator pattern can be used. Bernd has considered what is needed:
- Name of the database schema,
- Name of the stored procedure that is called
- None, one or more parameters passed to the stored procedure
- and, if a result is returned, a universal way to process the result.
class ExecutionArgs { public $schema = ''; public $procedure = ''; public $args = array(); public $resultHandler = null; ... }
Bernd’s solution is ExecutionArgs, or XArgs for short, similar to the Person class above, and is used to configure a database query (Listing 6). Two string fields for the names, an array to pick up the parameters passed to a stored procedure, and a function handler to process the result of the query.
A value of zero may be set for this handler in exceptional cases.. To be able to receive the result of a query later, $resultHandler is set with an anonymous function. It is also important to pay close attention to the order of the parameters in $args, otherwise undesired responses to the database can appear!
Listing 7 shows a section of the MySQL class. To call a stored procedure, use the execute (callable $config) method. It creates a new instance XArgs and calls the configurator for the object. This is followed by calling the query (…) method with the parameters querystring for the actual query and the result handler from XArgs for the result handling. The query of the database is executed here somewhat unusually as multi_query, which is however needed to be able to obtain several results from a stored procedure. But it also works for individual queries as usual. In MySQL, the functional iterator pattern is implemented in the each (…) method. There all answers (rows) are fetched from the database and the function $action($row) is called for each row, as long as it refers to a lambda and is not null.
class MYSQL { ... //------------------------------------------------------------------- function execute(callable $config): int { $xargs = new XArgs; $config($xargs); return $this->query($xargs->__toString(), $xargs->resultHandler); } //------------------------------------------------------------------- function query(string $query, callable $action): int { if($this->_db->multi_query($query)) do{ $result = $this->_db->store_result(); if( !is_bool($result) ) $this->each($result, $action); }while( $this->_db->more_results() && $this->_db->next_result() ); return $this->_db->errno; } //-------------------------------------------------------------------- private function each($result, callable $action): void { while($row = $result->fetch_object()) if($action !== null) $action($row); $result->free(); } //-------------------------------------------------------------------- ... }
Stream into HTML
Finally, the only thing missing is the calling part in the source code of the actual application. Listing 8 shows what that looks like in Bernd’s PHP program. There, a table with the clients for a country is output on the website, whereby here it is dynamically and directly rendered into the HTML stream of the server. After the HTML <tbody>, the PHP tag and the configurator for the SQL query is written directly. The parameters are set for scheme, procedure, searched country. The trick here is the value for the resultHandler. This is also written as an anonymous function, therefore a function in a function, and renders an HTML string directly into the server stream for each line of the database. There is another call in the program to dynamically create the dropdown for selecting a country. The source code for the required stored procedure can also be found in the project files.
... <table> <tbody> <?php $db->execute( function (XArgs $xArgs) { //---------------------------------------------- $xArgs->schema = 'classicmodels'; $xArgs->procedure = 'GET_CUSTOMERS_BY_COUNTRY'; $xArgs->args[] = 'germany'; //---------------------------------------------- $xArgs->resultHandler = function ($row) { echo ' <tr>' .' <td>'.$row->customer.'</td> ' .' <td>'.$row->name .'</td> ' .' <td>'.$row->city .'</td> ' .' <td>'.$row->country .'</td> ' .'</tr> '; }; //---------------------------------------------- }); ?> </tbody> </table> ...
As we have seen, functional programming is not just for handling data structures in memory; so-called push requests with (relational) databases can also be functionally and elegantly formulated. However, it is important to pay attention to good legibility of the source code, because when compared to other programming languages, PHP unfortunately offers a limited syntax for writing anonymous methods and the code may be illegible. In addition, functional techniques often allow for elegant, sometimes unconventional ways of solving often recurring tasks in the everyday life of a developer.
Links & literature
[1] http://php.net/manual/en/functions.anonymous.php
[2] http://www.mysqltutorial.org/wp-content/uploads/2018/03/mysqlsampledatabase.zip